/*
 * TowerFireControl.java
 *
 * Created on January 5, 2008, 4:10 PM
 *
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.tower;

import hextd.Constants;
import hextd.hex.HexTile;
import hextd.creep.HexTDCreep;
import hextd.util.Geometry;
import hextd.util.ParameterUtils;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.usoog.tdcore.creep.CreepListener;
import com.usoog.tdcore.exception.TileAlreadyHasTowerException;
import com.usoog.tdcore.exception.TileNotBuildableException;
import com.usoog.tdcore.player.Player;
import com.usoog.tdcore.tower.TowerListener;
import hextd.core.GameStateImplementation;

/**
 *
 * @author hylke
 */
public class TowerFireControl extends TowerBase implements TowerListener<HexTDTower>, CreepListener<HexTDCreep> {

	public static final Map<String, Long> price = new HashMap<String, Long>();

	static {
		price.put(Constants.RESOURCE_BONUS, 1l);
	}
	public static int damage = 0;
	public static double range = 2.5f;
	public static String name = "Fire Control";
	//
	private Color line2Color;
	private Color transColor;
	private int mode1 = 0;
	private int mode2 = 0;
	//
	public static double upgradeEffectRange = 0.5f;
	public static double upgradeEffectDamage = 0.5f;
	//
	private ArrayList<HexTDTower> clients;
	private Line2D[] clientLines;
	private Stroke clientStroke;
	private Color clientColor;
	private Map<HexTDTower, EnemyData> targetTable;
	private Map<HexTDCreep, EnemyData> lookupList = new HashMap<HexTDCreep, EnemyData>();
	private List<EnemyData> enemyData;
	private CompareCloseSame compCloseSame = new CompareCloseSame();
	private CompareCloseDiff compCloseDiff = new CompareCloseDiff();
	private CompareHardSame compHardSame = new CompareHardSame();
	private CompareHardDiff compHardDiff = new CompareHardDiff();
	private CompareWeakSame compWeakSame = new CompareWeakSame();
	private CompareWeakDiff compWeakDiff = new CompareWeakDiff();
	private CompareFarSame compFarSame = new CompareFarSame();
	private CompareFarDiff compFarDiff = new CompareFarDiff();
	String[] modes2 = {"Same", "Diff", "Diff++"};
	String[] modes1 = {"Close", "Hard", "Weak", "Furthest"};
	private Point2D.Double tempTargetGLoc = new Point2D.Double();
	private Point2D.Double tempTowerGLoc = new Point2D.Double();
	private Point2D.Double tempTargetPLoc = new Point2D.Double();

	private class EnemyData {

		public HexTDCreep enemy;
		public double dist2;
		public double health;
		public double progress;
		public int towerCount;
		public long nr;

		public EnemyData(HexTDCreep e) {
			enemy = e;
			nr = e.getId();
		}
	}

	private class CompareCloseSame implements Comparator<EnemyData> {

		Comparator<HexTDCreep> compare = new CreepCompare.CompareWeak();

		@Override
		public int compare(EnemyData e1, EnemyData e2) {
			if (e1.towerCount != e2.towerCount) {
				return e2.towerCount - e1.towerCount;
			} else {
				if (e1.dist2 != e2.dist2) {
					return java.lang.Double.compare(e1.dist2, e2.dist2);
				} else {
					return compare.compare(e1.enemy, e2.enemy);
				}
			}
		}
	}

	private class CompareCloseDiff implements Comparator<EnemyData> {

		Comparator<HexTDCreep> compare = new CreepCompare.CompareWeak();

		@Override
		public int compare(EnemyData e1, EnemyData e2) {
			if (e1.towerCount != e2.towerCount) {
				return e1.towerCount - e2.towerCount;
			} else {
				if (e1.dist2 != e2.dist2) {
					return java.lang.Double.compare(e1.dist2, e2.dist2);
				} else {
					return compare.compare(e1.enemy, e2.enemy);
				}
			}
		}
	}

	private class CompareHardSame implements Comparator<EnemyData> {

		Comparator<HexTDCreep> compare = new CreepCompare.CompareHard();

		@Override
		public int compare(EnemyData e1, EnemyData e2) {
			if (e1.towerCount != e2.towerCount) {
				return e2.towerCount - e1.towerCount;
			} else {
				return compare.compare(e1.enemy, e2.enemy);
			}
		}
	}

	private class CompareHardDiff implements Comparator<EnemyData> {

		Comparator<HexTDCreep> compare = new CreepCompare.CompareHard();

		@Override
		public int compare(EnemyData e1, EnemyData e2) {
			if (e1.towerCount != e2.towerCount) {
				return e1.towerCount - e2.towerCount;
			} else {
				return compare.compare(e1.enemy, e2.enemy);
			}
		}
	}

	private class CompareWeakSame implements Comparator<EnemyData> {

		Comparator<HexTDCreep> compare = new CreepCompare.CompareWeak();

		@Override
		public int compare(EnemyData e1, EnemyData e2) {
			if (e1.towerCount != e2.towerCount) {
				return e2.towerCount - e1.towerCount;
			} else {
				return compare.compare(e1.enemy, e2.enemy);
			}
		}
	}

	private class CompareWeakDiff implements Comparator<EnemyData> {

		Comparator<HexTDCreep> compare = new CreepCompare.CompareWeak();

		@Override
		public int compare(EnemyData e1, EnemyData e2) {
			if (e1.towerCount != e2.towerCount) {
				return e1.towerCount - e2.towerCount;
			} else {
				return compare.compare(e1.enemy, e2.enemy);
			}
		}
	}

	private class CompareFarSame implements Comparator<EnemyData> {

		Comparator<HexTDCreep> compare = new CreepCompare.CompareFurthest();

		@Override
		public int compare(EnemyData e1, EnemyData e2) {
			if (e1.towerCount != e2.towerCount) {
				return e2.towerCount - e1.towerCount;
			} else {
				return compare.compare(e2.enemy, e1.enemy);
			}
		}
	}

	private class CompareFarDiff implements Comparator<EnemyData> {

		Comparator<HexTDCreep> compare = new CreepCompare.CompareFurthest();

		@Override
		public int compare(EnemyData e1, EnemyData e2) {
			if (e1.towerCount != e2.towerCount) {
				return e1.towerCount - e2.towerCount;
			} else {
				return compare.compare(e2.enemy, e1.enemy);
			}
		}
	}

	/** Creates a new instance of TowerFireControl */
	public TowerFireControl() {
		super(FactoryTower.type.firecontrol, price, damage, range);
		lineColor = new Color(200, 200, 200);
		line2Color = new Color(255, 200, 200);
		clientColor = new Color(255, 200, 200);
		transColor = new Color(lineColor.getRed(), lineColor.getGreen(), lineColor.getBlue(), 100);
		clients = new ArrayList<HexTDTower>();

	}

	@Override
	public void init(
			GameStateImplementation gs,
			HexTile hex,
			Player owner,
			int activateTick)
			throws TileAlreadyHasTowerException, TileNotBuildableException {
		super.init(gs, hex, owner, activateTick);
		gameState.addTowerListener(this);
		gameState.addCreepListener(this);
		targetTable = new HashMap<HexTDTower, EnemyData>();
		enemyData = new ArrayList<EnemyData>();

		scanTowers();
		rescanCreeps();
	}

	@Override
	protected void makeButtons() {
		PanelTowerSettingsDouble p = new PanelTowerSettingsDouble(this, gameState.getGameManager());
		p.setModes(ModeType.Targeting, modes1);
		p.setMode(ModeType.Targeting, mode1, true);
		p.setModes(ModeType.Group, modes2);
		p.setMode(ModeType.Group, mode2, true);
		pts = p;
	}

	@Override
	protected boolean runActivate() {
		if (super.runActivate()) {
			scanTowers();
			return true;
		} else {
			return false;
		}
	}

	private void scanTowers() {
		Double loc = new Double();
		if (!selling && active) {
			double dx, dy;
			for (HexTDTower t : gameState.getTowers()) {
				if (!clients.contains(t) && t.getOwner() == getOwner()) {
					switch (t.getType()) {
						case firecontrol:
						case upgradeDamage:
						case upgradeRange:
							break;
						default:
							t.getTile().getGameLocation(loc);
							dx = gameLocation.x - loc.x;
							dy = gameLocation.y - loc.y;
							if ((dx * dx + dy * dy) < rangeCurrent2) {
								t.registerTower(this);
							}
							break;
					}
				} else {
					t.getTile().getGameLocation(loc);
					dx = gameLocation.x - loc.x;
					dy = gameLocation.y - loc.y;
					if (clients.contains(t) && (dx * dx + dy * dy) > rangeCurrent2) {
						t.unregisterTower(this);
					}
				}
			}
		}
	}

	@Override
	protected void calcDamageRange() {
		super.calcDamageRange();
		scanTowers();
	}

	@Override
	public boolean canUpgrade(String mode, boolean afterQueue) {
		return false;
	}

	@Override
	public String getStatusString() {
		String retString = "";
		retString += "<h1>Fire Control</h1>"
				+ "<p>"
				+ "Controls the targeting of other towers and adds 25% damage and range.<br>"
				+ "<b>Towers controlled:</b> " + clients.size() + "<br>\n"
				+ "<b>Range:</b> " + rangeText + "<br>"
				+ "</p>"
				+ "<p><b>R</b>: Next control mode</p>\n";
		return retString;
	}

	@Override
	public String getDescription() {
		String retString = "";
		retString += "<h1>Fire Control</h1>"
				+ "<p>"
				+ "This tower controls the targeting of other towers and can force them to fire at the same, or different targets. "
				+ "Additionally, it increases damage and range of controlled towers  by "
				+ upgradeEffectDamage + " and  by " + upgradeEffectRange + ""
				+ "It does not contol Blue Towers 1 & 2."
				+ "</p><p class='warn'>This tower costs 1 bonus point.</p>";
		return retString;
	}

	@Override
	public void runTick(int gameTime) {
	}

	@Override
	public void towerAdded(HexTDTower t) {
		Double loc;
		if (t != this && !selling && !clients.contains(t) && t.getOwner() == getOwner()) {
			switch (t.getType()) {
				case firecontrol:
				case upgradeDamage:
				case upgradeRange:
					break;
				default:
					double dx;
					double dy;
					loc = t.getTile().getGameLocation(null);
					dx = gameLocation.x - loc.x;
					dy = gameLocation.y - loc.y;
					if ((dx * dx + dy * dy) < rangeCurrent2) {
						t.registerTower(this);
					}
					break;
			}
		}
	}

	@Override
	public void towerRemoved(HexTDTower t) {
		if (clients.contains(t)) {
			t.unregisterTower(this);
		}
	}

	public void addClient(HexTDTower t) {
		if (!selling && !clients.contains(t)) {
			clients.add(t);
			generateShapes();
		}
	}

	public void removeClient(HexTDTower t) {
		clients.remove(t);
		TowerFireControl.EnemyData ed = targetTable.get(t);
		if (ed != null) {
			ed.towerCount--;
			targetTable.remove(t);
		}
		generateShapes();
	}

	@Override
	public void generateShapes() {
		int shapeCount = 13;
		int i;

		double cx = +paintScale * 1 / 4;
		double cy = -paintScale / 5;
		double[][] points1 = Geometry.createHexCoords(paintScale / 2, cx, cy);
		double[][] points2 = Geometry.createHexCoords(paintScale);

		i = 0;
		shapes = new Shape[shapeCount];
		shapes[i++] = new Line2D.Double(cx, cy, points1[0][0], points1[0][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points1[1][0], points1[1][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points1[2][0], points1[2][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points1[3][0], points1[3][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points1[4][0], points1[4][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points1[5][0], points1[5][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points2[0][0], points2[0][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points2[1][0], points2[1][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points2[2][0], points2[2][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points2[3][0], points2[3][1]);
		shapes[i++] = new Line2D.Double(cx, cy, points2[5][0], points2[5][1]);
		shapes[i++] = Geometry.createCircle(cx, cy, paintScale / 2);
		shapes[i++] = Geometry.createCircle(cx, cy, paintScale / 2);

		shapeAction = new int[shapeCount];
		for (i = 0; i < shapeCount; i++) {
			shapeAction[i] = 1;
		}
		shapeAction[11] = 0;

		shapeColor = new Color[shapeCount];
		for (i = 0; i < 6; i++) {
			shapeColor[i] = line2Color;
		}
		for (i = 6; i < shapeCount; i++) {
			shapeColor[i] = lineColor;
		}
		shapeColor[11] = transColor;

		i = 0;
		strokes = new Stroke[shapeCount];
		strokes[i++] = new BasicStroke(1.5f * strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
		strokes[i++] = strokes[0];
		strokes[i++] = strokes[0];
		strokes[i++] = strokes[0];
		strokes[i++] = strokes[0];
		strokes[i++] = strokes[0];
		strokes[i++] = new BasicStroke(1.0f * strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
		strokes[i++] = strokes[6];
		strokes[i++] = strokes[6];
		strokes[i++] = strokes[6];
		strokes[i++] = strokes[6];
		strokes[i++] = strokes[6];
		strokes[i++] = strokes[6];

		clientLines = new Line2D[clients.size()];
		Double from = new Double();
		Point2D.Double to = new Point2D.Double(paintLocation.x + cx, paintLocation.y + cy);
		for (i = 0; i < clientLines.length; i++) {
			from = clients.get(i).getTile().getPaintLocation(from);
			clientLines[i] = new Line2D.Double(from.x, from.y, to.x, to.y);
		}
		clientStroke = new BasicStroke(1.0f * strokeScale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL);
	}

	@Override
	public void paintEffects(Graphics2D g2, int gameTime) {
		if (selected) {
			g2.setStroke(clientStroke);
			g2.setColor(clientColor);
			for (int i = 0; i < clientLines.length; i++) {
				g2.draw(clientLines[i]);
			}
		}
	}

	@Override
	public void delete() {
		super.delete();
		for (int i = clients.size() - 1; i >= 0; i--) {
			HexTDTower t = clients.get(i);
			t.unregisterTower(this);
		}
		gameState.removeTowerListener(this);
		gameState.removeCreepListener(this);
	}

	public void rescanCreeps() {
		Collection<HexTDCreep> targets = gameState.getTargets(getOwner());
		enemyData.clear();
		lookupList.clear();
		for (HexTDCreep e : targets) {
			EnemyData enemyData1 = new EnemyData(e);
			enemyData.add(enemyData1);
			lookupList.put(e, enemyData1);
		}
		targetTable.clear();
	}

	public void creepAdded(HexTDCreep c) {
		EnemyData enemyData1 = new EnemyData(c);
		enemyData.add(enemyData1);
		lookupList.put(c, enemyData1);
	}

	public void creepRemoved(HexTDCreep c) {
		EnemyData remove = lookupList.remove(c);
		enemyData.remove(remove);
	}

	@Override
	protected boolean activateSetMode(String m, String v) {
		int val;
		val = Integer.parseInt(v);
		ModeType modeType = ModeType.valueOf(m);
		switch (modeType) {
			case Targeting:
				ParameterUtils.forceIndexBounds(modes1, val);
				mode1 = val;

				retarget = true;
				System.out.println("TowerFireControl::setMode: Setting to " + modes1[mode1]);
				if (pts != null && pts.getMode(modeType) != val) {
					pts.setMode(modeType, val, true);
				}
				return true;
			case Group:
				ParameterUtils.forceIndexBounds(modes2, val);
				mode2 = val;

				retarget = true;
				System.out.println("TowerFireControl::setMode: Setting to " + modes2[mode2]);
				if (pts != null && pts.getMode(modeType) != val) {
					pts.setMode(modeType, val, true);
				}
				return true;
			default:
				System.err.println("TowerFireControl::setMode: unknown mode: " + m);
				return false;
		}
	}

	@Override
	public String getMode(String m) {
		ModeType valueOf = ModeType.valueOf(m);
		switch (valueOf) {
			case Targeting:
				return "" + mode1;
			case Group:
				return "" + mode2;
			default:
				System.err.println("TowerBase::setMode: unknown mode: " + m);
				return "-1";
		}
	}

	public void updateEnemies(double x, double y) {
		double dx, dy;
		for (EnemyData e : enemyData) {
			e.enemy.getGameLocation(tempTargetGLoc);
			dx = x - tempTargetGLoc.x;
			dy = y - tempTargetGLoc.y;
			e.dist2 = dx * dx + dy * dy;
			e.health = e.enemy.getHealth();
			e.progress = e.enemy.getDistance();
		}
	}

	public HexTDCreep findEnemy(HexTDTower t, HexTDCreep preferedEnemy, boolean exShielded) {
		if (retarget) {
			preferedEnemy = null;
			retarget = false;
		}
		switch (mode1) {
			case 0:
				return findEnemyClosest(t, preferedEnemy, exShielded);
			case 1:
				return findEnemyHardest(t, preferedEnemy, exShielded);
			case 2:
				return findEnemyWeakest(t, preferedEnemy, exShielded);
			case 3:
				return findEnemyFurthest(t, preferedEnemy, exShielded);
			default:
				System.err.println("TowerFireControl::FindEnemy: Unknown mode1 " + mode1);
		}
		return null;
	}

	private HexTDCreep findEnemyClosest(HexTDTower t, HexTDCreep preferedEnemy, boolean exShielded) {
		return findEnemy(t, preferedEnemy, exShielded, compCloseSame, compCloseDiff);
	}

	private HexTDCreep findEnemyHardest(HexTDTower t, HexTDCreep preferedEnemy, boolean exShielded) {
		return findEnemy(t, preferedEnemy, exShielded, compHardSame, compHardDiff);
	}

	private HexTDCreep findEnemyWeakest(HexTDTower t, HexTDCreep preferedEnemy, boolean exShielded) {
		return findEnemy(t, preferedEnemy, exShielded, compWeakSame, compWeakDiff);
	}

	private HexTDCreep findEnemyFurthest(HexTDTower t, HexTDCreep preferedEnemy, boolean exShielded) {
		return findEnemy(t, preferedEnemy, exShielded, compFarSame, compFarDiff);
	}

	private HexTDCreep findEnemy(HexTDTower t, HexTDCreep preferedEnemy, boolean exShielded, Comparator<EnemyData> cSame, Comparator<EnemyData> cDiff) {
		double r2 = t.getRangeCurrent();
		r2 *= r2;
		Double loc = t.getTile().getGameLocation(tempTowerGLoc);
		updateEnemies(loc.x, loc.y);

		boolean force = false;
		HexTDCreep e;
		EnemyData oldEd;
		oldEd = targetTable.get(t);

		switch (mode2) {
			case 0:
				Collections.sort(enemyData, cSame);
				break;
			case 2:
				force = true;
			case 1:
				if ((oldEd != null) && (oldEd.dist2 < r2) && (oldEd.towerCount == 1)) {
					if (oldEd.enemy.isValidTarget() && !(exShielded && oldEd.enemy.isShielded())) {
						return oldEd.enemy;
					} else {
						oldEd.towerCount--;
						targetTable.remove(t);
						oldEd = null;
					}
				}
				Collections.sort(enemyData, cDiff);
				break;
		}

		for (EnemyData ed : enemyData) {
			e = ed.enemy;
			if (e.isValidTarget() && !(exShielded && e.isShielded())) {
				if (ed.dist2 < r2 && !(force && ed.towerCount > 0)) {
					// this is our target!
					if (ed != oldEd) {
						// we changed targets!
						if (oldEd != null) {
							oldEd.towerCount--;
						}
						ed.towerCount++;
						targetTable.put(t, ed);
					}
					return ed.enemy;
				}
			}
		}
		if (oldEd != null) {
			oldEd.towerCount--;
			targetTable.remove(t);
		}
		return null;
	}
}
